
from formula import *
import random as r
import numpy as np

np.seterr(all='raise')

class Gene(object):
	def __init__(self, signal, x):
		self.signal = signal
		self.x = x
		self._initFormulasList()

	@staticmethod
	def initialisedGene(signal, x):
		gene = Gene(signal, x)
		gene.initPropSignal()
		gene.initWithCteFormula()
		return gene

	def _initFormulasList(self):
		self.formulas = []
		self.hashFormulas = {}
		self.cteFormulas = []
		self.hashCteFormulas = {}

	def initPropSignal(self):
		self.lenSignal = len(self.signal)
		self.meanSignal = self.signal.mean()

	def initWithCteFormula(self):
		self.formula = ConstanteLeaf(self, self, self.meanSignal)
		self.maxDistanceToSignal = self.getDistanceToSignal()
		self.lastDistanceToSignal = self.maxDistanceToSignal

	def clone(self):
		clonedGene = Gene(self.signal, self.x)
		clonedGene.lenSignal = self.lenSignal
		clonedGene.meanSignal = self.meanSignal
		clonedGene.maxDistanceToSignal = self.maxDistanceToSignal
		clonedGene.lastDistanceToSignal = self.lastDistanceToSignal
		clonedGene.formula = self.formula.clone(clonedGene, clonedGene)
		return clonedGene

	def __str__(self):
		return self.formula.__str__()

	def register(self, formula):
		if(type(formula) == ConstanteLeaf):
			index = len(self.cteFormulas)
			self.cteFormulas.append(formula)
			self.hashCteFormulas[formula] = index
		index = len(self.formulas)
		self.formulas.append(formula)
		self.hashFormulas[formula] = index

	def change(self):
		if len(self.cteFormulas) > 0:
			index = r.randrange(0, len(self.cteFormulas))
			cFormula = self.cteFormulas[index]
			cFormula.change()
			#shift = (r.random() - 0.5)*sqrt(abs(cFormula.constante))
			#cFormula.constante += shift
		return self


	def easyMutate(self):
		FormulaClass = self.getRandomFormulaClass()
		baseFormula = self.getRandomBaseFormula()
		parentBaseFormula = baseFormula.parent
		newFormula = FormulaClass(self, baseFormula.parent)
		newFormula.generateChilds(baseFormula)
		parentBaseFormula.replaceChild(baseFormula, newFormula)

	def replaceChild(self, oldChild, newChild):
		self.formula = newChild

	def getRandomBaseFormula(self):
		index = r.randrange(0, len(self.formulas))
		baseFormula = self.formulas[index]
		return baseFormula

	def getRandomFormulaClass(self):
		index = r.randrange(0, len(ALL_FORMULAS))
		FormulaClass = ALL_FORMULAS[index]
		return FormulaClass

	def advanceMutate(self):
		for x in xrange(3):
			self.easyMutate()

	def dualMutation(self, other):
		selfFormula = self.getRandomBaseFormula()
		selfParentFormula = selfFormula.parent
		otherFormula = other.getRandomBaseFormula()
		otherParentFormula = otherFormula.parent
		selfParentFormula.replaceChild(selfFormula, otherFormula)
		otherParentFormula.replaceChild(otherFormula, selfFormula)
		selfFormula.parent, otherFormula.parent = otherFormula.parent, selfFormula.parent
		self.restartRegistration()
		other.restartRegistration()
		return self

	def restartRegistration(self):
		self._initFormulasList()
		self.formula.restartRegistration(self)

	def getEstimatedSignal(self):
		vectorizedFormula = np.vectorize(self.formula)
		estimatedSignal = vectorizedFormula(self.x)
		return estimatedSignal

	def getDistanceToSignal(self):
		try:
			estimatedSignal = self.getEstimatedSignal()
			self.lastDistanceToSignal = np.linalg.norm(self.signal - estimatedSignal)
			return self.lastDistanceToSignal
		except (ZeroDivisionError, TypeError, ValueError, OverflowError, FloatingPointError):
			return -1

	def getRelativeDistanceToSignal(self):
		if self.lastDistanceToSignal < 0:
			return -1
		return self.lastDistanceToSignal/self.maxDistanceToSignal

class GeneTwoDim(Gene):
	def __init__(self, signal, x, y):
		super(GeneTwoDim, self).__init__(signal, x)
		self.y = y

	@staticmethod
	def initialisedGene(signal, x, y):
		gene = GeneTwoDim(signal, x, y)
		gene.initPropSignal()
		gene.initWithCteFormula()
		return gene

	def clone(self):
		clonedGene = GeneTwoDim(self.signal, self.x, self.y)
		clonedGene.lenSignal = self.lenSignal
		clonedGene.meanSignal = self.meanSignal
		clonedGene.maxDistanceToSignal = self.maxDistanceToSignal
		clonedGene.lastDistanceToSignal = self.lastDistanceToSignal
		clonedGene.formula = self.formula.clone(clonedGene, clonedGene)
		return clonedGene

	def getEstimatedSignal(self):
		vectorizedFormula = np.vectorize(self.formula)
		estimatedSignal = vectorizedFormula(self.x, self.y)
		return estimatedSignal



if __name__ == '__main__':
	#r.seed(1)
	YLeaf = XLeaf
	signal = np.array([1,12,1,13,2,12,1,5])
	x = np.arange(len(signal))
	gene = Gene.initialisedGene(signal, x)
	print "Initial gene:"
	print "gene:", gene
	print "\ngene.easyMutate():"
	gene.easyMutate()
	print "gene.formula.childs:", gene.formula.childs
	print "gene:", gene
	print "\ngene.easyMutate():"
	gene.easyMutate()
	print "gene:", gene
	print "\ngene.advanceMutate():"
	gene.advanceMutate()
	print "gene:", gene
	print "\ngene.change():"
	gene.change()
	print "gene:", gene
	print "distance:", gene.getDistanceToSignal()
	print "relative distance:", gene.getRelativeDistanceToSignal()
	print "len(gene.formulas):", len(gene.formulas)
	print '=========================================='
	clonedGene = gene.clone()
	print "\ngene.clone():"
	print "len(clonedGene.formulas):", len(clonedGene.formulas)
	print "clonedGene:", clonedGene
	print "distance:", clonedGene.getDistanceToSignal()
	print "relative distance:", clonedGene.getRelativeDistanceToSignal()
	gene2 = Gene.initialisedGene(signal, x)
	gene2.advanceMutate()
	gene2.advanceMutate()
	print '=========================================='
	print 'gene2:', gene2
	gene.dualMutation(gene2)
	print '=========================================='
	print 'gene.dualMutation(gene2):'
	print 'gene after mutation:', gene
	print 'gene2 after mutation:', gene2
	clonedGene = gene.clone()
	clonedGene2 = gene2.clone()
	assert(len(clonedGene.formulas) == len(gene.formulas))
	assert(len(clonedGene2.formulas) == len(clonedGene2.formulas))
	for formula in gene.formulas:
		assert(formula not in clonedGene.formulas)
	for formula2 in gene2.formulas:
		assert(formula2 not in clonedGene2.formulas)


